package com.zenika.zentracker.hibernate;

import java.io.Serializable;
import java.sql.Connection;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.CacheMode;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.Filter;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.ReplicationMode;
import org.hibernate.SQLQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.engine.ActionQueue;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.EntityKey;
import org.hibernate.engine.PersistenceContext;
import org.hibernate.engine.QueryParameters;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
import org.hibernate.event.EventListeners;
import org.hibernate.event.EventSource;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.jdbc.Batcher;
import org.hibernate.jdbc.JDBCContext;
import org.hibernate.loader.custom.CustomQuery;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.SessionStatistics;
import org.hibernate.type.Type;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.jmx.export.MBeanExportException;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.support.JmxUtils;

import com.zenika.zentracker.spring.HttpRequestInfoBean;
import com.zenika.zentracker.spring.WebZenTrackerConfigurer;
import com.zenika.zentracker.spring.ZenTrackerConfigurer;

public class ZenTrackerSession implements EventSource, Session, JDBCContext.Context {
    private static final long serialVersionUID = -6076602694663577071L;

    private static final Log log = LogFactory.getLog("com.zenika.ZenTracker");
    
    private Session currentSession;
    private EventSource currentEventSource;
    private JDBCContext.Context currentContext;
    private ApplicationContext applicationContext;
    private Boolean exportJMXStatistics;

    public ZenTrackerSession(Session hibernateSession) {
        this(hibernateSession, null);
    }

    public ZenTrackerSession(Session hibernateSession, ApplicationContext applicationContext) {
        this.currentSession = hibernateSession;
        this.currentEventSource = (EventSource) hibernateSession;
        this.currentContext = (JDBCContext.Context) hibernateSession;
        this.applicationContext = applicationContext;
        exportJMXStatistics = ((Boolean) applicationContext.getBean(ZenTrackerConfigurer.BOOLEAN_EXPORT_JMX_BEAN));
    }

    private MBeanExporter getMBeanExporter() {
        if (applicationContext == null)
            return null;
        
        return (MBeanExporter) applicationContext.getBean(ZenTrackerConfigurer.MBEAN_EXPORTER_BEAN_ALIAS);
    }
    
    public Transaction beginTransaction() throws HibernateException {
        return currentSession.beginTransaction();
    }

    public void cancelQuery() throws HibernateException {
        currentSession.cancelQuery();
    }

    public void clear() {
        currentSession.clear();
    }

    public Connection close() throws HibernateException {
        // Logging Session Statistics before closing the Session
        Interceptor interceptor = currentEventSource.getInterceptor();
        
        if (ZenTrackerInterceptor.class.isAssignableFrom(interceptor.getClass())) {
            ((ZenTrackerInterceptor) interceptor).logSummary();    
        }

        if (exportJMXStatistics.booleanValue()) {
            exportJMXStatistics();
        }

        return currentSession.close();
    }

    private void exportJMXStatistics() {
        MBeanExporter exporter = getMBeanExporter();
        if (exporter != null) {
            try {
                if (exporter.getServer() == null) {
                    exporter.setServer(JmxUtils.locateMBeanServer());    
                }
                exporter.registerManagedResource(((ZenTrackerInterceptor) getInterceptor()).buildJMXStats(), createObjectName());
            } catch (MBeanExportException e) {
                log.debug("EXCEPTION while exporting the JMX statistics" + e.getMessage());
            }
        }
    }

    private ObjectName createObjectName() {
        ObjectName objectName = null;
        
        StringBuffer bufferObjectName = new StringBuffer();
        bufferObjectName.append("ZenTracker:");
        
        
        HttpRequestInfoBean requestInfoBean = null;
        try {
            requestInfoBean = (HttpRequestInfoBean) applicationContext.getBean(WebZenTrackerConfigurer.REQUEST_STATISTICS_BEAN); 
        } catch (NoSuchBeanDefinitionException nsbde) {
            // Do nothing if bean not present
        }
         
        if (requestInfoBean != null) {
            bufferObjectName
                .append("session=").append(requestInfoBean.getSessionInfo())
                .append(",request=").append(requestInfoBean.getRequestInfo())
                .append(",execution=").append(System.currentTimeMillis());
        } else {
            bufferObjectName.append("name=").append(System.currentTimeMillis());
        }
        
        ////localhost/resanet,J2EEApplication=tomcat,J2EEServer=
        try {
            objectName = new ObjectName(bufferObjectName.toString());
        } catch (MalformedObjectNameException e) {
            log.debug("EXCEPTION while exporting the JMX statistics" + e.getMessage());
        }
        
        return objectName;
    }
    
    public Connection connection() throws HibernateException {
        return currentSession.connection();
    }

    public boolean contains(Object object) {
        return currentSession.contains(object);
    }

    public Criteria createCriteria(Class persistentClass, String alias) {
        return currentSession.createCriteria(persistentClass, alias);
    }

    public Criteria createCriteria(Class persistentClass) {
        return currentSession.createCriteria(persistentClass);
    }

    public Criteria createCriteria(String entityName, String alias) {
        return currentSession.createCriteria(entityName, alias);
    }

    public Criteria createCriteria(String entityName) {
        return currentSession.createCriteria(entityName);
    }

    public Query createFilter(Object collection, String queryString) throws HibernateException {
        return currentSession.createFilter(collection, queryString);
    }

    public Query createQuery(String queryString) throws HibernateException {
        return currentSession.createQuery(queryString);
    }

    public Query createSQLQuery(String sql, String returnAlias, Class returnClass) {
        return currentSession.createSQLQuery(sql, returnAlias, returnClass);
    }

    public Query createSQLQuery(String sql, String[] returnAliases, Class[] returnClasses) {
        return currentSession.createSQLQuery(sql, returnAliases, returnClasses);
    }

    public SQLQuery createSQLQuery(String queryString) throws HibernateException {
        return currentSession.createSQLQuery(queryString);
    }

    public void delete(Object object) throws HibernateException {
        currentSession.delete(object);
    }

    public int delete(String query, Object value, Type type) throws HibernateException {
        return currentSession.delete(query, value, type);
    }

    public void delete(String entityName, Object object) throws HibernateException {
        currentSession.delete(entityName, object);
    }

    public int delete(String query, Object[] values, Type[] types) throws HibernateException {
        return currentSession.delete(query, values, types);
    }

    public int delete(String query) throws HibernateException {
        return currentSession.delete(query);
    }

    public void disableFilter(String filterName) {
        currentSession.disableFilter(filterName);
    }

    public Connection disconnect() throws HibernateException {
        return currentSession.disconnect();
    }

    public Filter enableFilter(String filterName) {
        return currentSession.enableFilter(filterName);
    }

    public void evict(Object object) throws HibernateException {
        currentSession.evict(object);
    }

    public Collection filter(Object collection, String filter, Object value, Type type) throws HibernateException {
        return currentSession.filter(collection, filter, value, type);
    }

    public Collection filter(Object collection, String filter, Object[] values, Type[] types) throws HibernateException {
        return currentSession.filter(collection, filter, values, types);
    }

    public Collection filter(Object collection, String filter) throws HibernateException {
        return currentSession.filter(collection, filter);
    }

    public List find(String query, Object value, Type type) throws HibernateException {
        return currentSession.find(query, value, type);
    }

    public List find(String query, Object[] values, Type[] types) throws HibernateException {
        return currentSession.find(query, values, types);
    }

    public List find(String query) throws HibernateException {
        return currentSession.find(query);
    }

    public void flush() throws HibernateException {
        currentSession.flush();
    }

    public Object get(Class clazz, Serializable id, LockMode lockMode) throws HibernateException {
        return currentSession.get(clazz, id, lockMode);
    }

    public Object get(Class clazz, Serializable id) throws HibernateException {
        return currentSession.get(clazz, id);
    }

    public Object get(String entityName, Serializable id, LockMode lockMode) throws HibernateException {
        return currentSession.get(entityName, id, lockMode);
    }

    public Object get(String entityName, Serializable id) throws HibernateException {
        return currentSession.get(entityName, id);
    }

    public CacheMode getCacheMode() {
        return currentSession.getCacheMode();
    }

    public LockMode getCurrentLockMode(Object object) throws HibernateException {
        return currentSession.getCurrentLockMode(object);
    }

    public Filter getEnabledFilter(String filterName) {
        return currentSession.getEnabledFilter(filterName);
    }

    public EntityMode getEntityMode() {
        return currentSession.getEntityMode();
    }

    public String getEntityName(Object object) throws HibernateException {
        return currentSession.getEntityName(object);
    }

    public FlushMode getFlushMode() {
        return currentSession.getFlushMode();
    }

    public Serializable getIdentifier(Object object) throws HibernateException {
        return currentSession.getIdentifier(object);
    }

    public Query getNamedQuery(String queryName) throws HibernateException {
        return currentSession.getNamedQuery(queryName);
    }

    public org.hibernate.Session getSession(EntityMode entityMode) {
        return currentSession.getSession(entityMode);
    }

    public SessionFactory getSessionFactory() {
        return currentSession.getSessionFactory();
    }

    public SessionStatistics getStatistics() {
        return currentSession.getStatistics();
    }

    public Transaction getTransaction() {
        return currentSession.getTransaction();
    }

    public boolean isConnected() {
        return currentSession.isConnected();
    }

    public boolean isDirty() throws HibernateException {
        return currentSession.isDirty();
    }

    public boolean isOpen() {
        return currentSession.isOpen();
    }

    public Iterator iterate(String query, Object value, Type type) throws HibernateException {
        return currentSession.iterate(query, value, type);
    }

    public Iterator iterate(String query, Object[] values, Type[] types) throws HibernateException {
        return currentSession.iterate(query, values, types);
    }

    public Iterator iterate(String query) throws HibernateException {
        return currentSession.iterate(query);
    }

    public Object load(Class theClass, Serializable id, LockMode lockMode) throws HibernateException {
        return currentSession.load(theClass, id, lockMode);
    }

    public Object load(Class theClass, Serializable id) throws HibernateException {
        return currentSession.load(theClass, id);
    }

    public void load(Object object, Serializable id) throws HibernateException {
        currentSession.load(object, id);
    }

    public Object load(String entityName, Serializable id, LockMode lockMode) throws HibernateException {
        return currentSession.load(entityName, id, lockMode);
    }

    public Object load(String entityName, Serializable id) throws HibernateException {
        return currentSession.load(entityName, id);
    }

    public void lock(Object object, LockMode lockMode) throws HibernateException {
        currentSession.lock(object, lockMode);
    }

    public void lock(String entityName, Object object, LockMode lockMode) throws HibernateException {
        currentSession.lock(entityName, object, lockMode);
    }

    public Object merge(Object object) throws HibernateException {
        return currentSession.merge(object);
    }

    public Object merge(String entityName, Object object) throws HibernateException {
        return currentSession.merge(entityName, object);
    }

    public void persist(Object object) throws HibernateException {
        currentSession.persist(object);
    }

    public void persist(String entityName, Object object) throws HibernateException {
        currentSession.persist(entityName, object);
    }

    public void reconnect() throws HibernateException {
        currentSession.reconnect();
    }

    public void reconnect(Connection connection) throws HibernateException {
        currentSession.reconnect(connection);
    }

    public void refresh(Object object, LockMode lockMode) throws HibernateException {
        currentSession.refresh(object, lockMode);
    }

    public void refresh(Object object) throws HibernateException {
        currentSession.refresh(object);
    }

    public void replicate(Object object, ReplicationMode replicationMode) throws HibernateException {
        currentSession.replicate(object, replicationMode);
    }

    public void replicate(String entityName, Object object, ReplicationMode replicationMode) throws HibernateException {
        currentSession.replicate(entityName, object, replicationMode);
    }

    public void save(Object object, Serializable id) throws HibernateException {
        currentSession.save(object, id);
    }

    public Serializable save(Object object) throws HibernateException {
        return currentSession.save(object);
    }

    public void save(String entityName, Object object, Serializable id) throws HibernateException {
        currentSession.save(entityName, object, id);
    }

    public Serializable save(String entityName, Object object) throws HibernateException {
        return currentSession.save(entityName, object);
    }

    public void saveOrUpdate(Object object) throws HibernateException {
        currentSession.saveOrUpdate(object);
    }

    public void saveOrUpdate(String entityName, Object object) throws HibernateException {
        currentSession.saveOrUpdate(entityName, object);
    }

    public Object saveOrUpdateCopy(Object object, Serializable id) throws HibernateException {
        return currentSession.saveOrUpdateCopy(object, id);
    }

    public Object saveOrUpdateCopy(Object object) throws HibernateException {
        return currentSession.saveOrUpdateCopy(object);
    }

    public Object saveOrUpdateCopy(String entityName, Object object, Serializable id) throws HibernateException {
        return currentSession.saveOrUpdateCopy(entityName, object, id);
    }

    public Object saveOrUpdateCopy(String entityName, Object object) throws HibernateException {
        return currentSession.saveOrUpdateCopy(entityName, object);
    }

    public void setCacheMode(CacheMode cacheMode) {
        currentSession.setCacheMode(cacheMode);
    }

    public void setFlushMode(FlushMode flushMode) {
        currentSession.setFlushMode(flushMode);
    }

    public void setReadOnly(Object entity, boolean readOnly) {
        currentSession.setReadOnly(entity, readOnly);
    }

    public void update(Object object, Serializable id) throws HibernateException {
        currentSession.update(object, id);
    }

    public void update(Object object) throws HibernateException {
        currentSession.update(object);
    }

    public void update(String entityName, Object object, Serializable id) throws HibernateException {
        currentSession.update(entityName, object, id);
    }

    public void update(String entityName, Object object) throws HibernateException {
        currentSession.update(entityName, object);
    }

    /** ******* EVENT SOURCE ********** */
    /** ******* EVENT SOURCE ********** */
    /** ******* EVENT SOURCE ********** */

    public void delete(String entityName, Object child, boolean isCascadeDeleteEnabled, Set transientEntities) {
        currentEventSource.delete(entityName, child, isCascadeDeleteEnabled, transientEntities);
    }

    public void forceFlush(EntityEntry e) throws HibernateException {
        currentEventSource.forceFlush(e);
    }

    public ActionQueue getActionQueue() {
        return currentEventSource.getActionQueue();
    }

    public Object instantiate(EntityPersister persister, Serializable id) throws HibernateException {
        return currentEventSource.instantiate(persister, id);
    }

    public void merge(String entityName, Object object, Map copiedAlready) throws HibernateException {
        currentEventSource.merge(entityName, object, copiedAlready);
    }

    public void persist(String entityName, Object object, Map createdAlready) throws HibernateException {
        currentEventSource.persist(entityName, object, createdAlready);
    }

    public void persistOnFlush(String entityName, Object object, Map copiedAlready) {
        currentEventSource.persistOnFlush(entityName, object, copiedAlready);
    }

    public void refresh(Object object, Map refreshedAlready) throws HibernateException {
        currentEventSource.refresh(object, refreshedAlready);
    }

    public void saveOrUpdateCopy(String entityName, Object object, Map copiedAlready) throws HibernateException {
        currentEventSource.saveOrUpdateCopy(entityName, object, copiedAlready);
    }

    public void afterScrollOperation() {
        currentEventSource.afterScrollOperation();
    }

    public void afterTransactionCompletion(boolean successful, Transaction tx) {
        currentEventSource.afterTransactionCompletion(successful, tx);
    }

    public void beforeTransactionCompletion(Transaction tx) {
        currentEventSource.beforeTransactionCompletion(tx);
    }

    public String bestGuessEntityName(Object object) {
        return currentEventSource.bestGuessEntityName(object);
    }

    public int executeNativeUpdate(NativeSQLQuerySpecification specification, QueryParameters queryParameters)
            throws HibernateException {
        return currentEventSource.executeNativeUpdate(specification, queryParameters);
    }

    public int executeUpdate(String query, QueryParameters queryParameters) throws HibernateException {
        return currentEventSource.executeUpdate(query, queryParameters);
    }

    public Batcher getBatcher() {
        return currentEventSource.getBatcher();
    }

    public Serializable getContextEntityIdentifier(Object object) {
        return currentEventSource.getContextEntityIdentifier(object);
    }

    public int getDontFlushFromFind() {
        return currentEventSource.getDontFlushFromFind();
    }

    public Map getEnabledFilters() {
        return currentEventSource.getEnabledFilters();
    }

    public EntityPersister getEntityPersister(String entityName, Object object) throws HibernateException {
        return currentEventSource.getEntityPersister(entityName, object);
    }

    public Object getEntityUsingInterceptor(EntityKey key) throws HibernateException {
        return currentEventSource.getEntityUsingInterceptor(key);
    }

    public SessionFactoryImplementor getFactory() {
        return currentEventSource.getFactory();
    }

    public String getFetchProfile() {
        return currentEventSource.getFetchProfile();
    }

    public Type getFilterParameterType(String filterParameterName) {
        return currentEventSource.getFilterParameterType(filterParameterName);
    }

    public Object getFilterParameterValue(String filterParameterName) {
        return currentEventSource.getFilterParameterValue(filterParameterName);
    }

    public Interceptor getInterceptor() {
        return currentEventSource.getInterceptor();
    }

    public JDBCContext getJDBCContext() {
        return currentEventSource.getJDBCContext();
    }

    public EventListeners getListeners() {
        return currentEventSource.getListeners();
    }

    public Query getNamedSQLQuery(String name) {
        return currentEventSource.getNamedSQLQuery(name);
    }

    public PersistenceContext getPersistenceContext() {
        return currentEventSource.getPersistenceContext();
    }

    public long getTimestamp() {
        return currentEventSource.getTimestamp();
    }

    public String guessEntityName(Object entity) throws HibernateException {
        return currentEventSource.guessEntityName(entity);
    }

    public Object immediateLoad(String entityName, Serializable id) throws HibernateException {
        return currentEventSource.immediateLoad(entityName, id);
    }

    public void initializeCollection(PersistentCollection collection, boolean writing) throws HibernateException {
        currentEventSource.initializeCollection(collection, writing);
    }

    public Object instantiate(String entityName, Serializable id) throws HibernateException {
        return currentEventSource.instantiate(entityName, id);
    }

    public Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable)
            throws HibernateException {
        return currentEventSource.internalLoad(entityName, id, eager, nullable);
    }

    public boolean isClosed() {
        return currentEventSource.isClosed();
    }

    public boolean isEventSource() {
        return currentEventSource.isEventSource();
    }

    public boolean isTransactionInProgress() {
        return currentEventSource.isTransactionInProgress();
    }

    public Iterator iterate(String query, QueryParameters queryParameters) throws HibernateException {
        return currentEventSource.iterate(query, queryParameters);
    }

    public Iterator iterateFilter(Object collection, String filter, QueryParameters queryParameters)
            throws HibernateException {
        return currentEventSource.iterateFilter(collection, filter, queryParameters);
    }

    public List list(CriteriaImpl criteria) {
        return currentEventSource.list(criteria);
    }

    public List list(String query, QueryParameters queryParameters) throws HibernateException {
        return currentEventSource.list(query, queryParameters);
    }

    public List list(NativeSQLQuerySpecification spec, QueryParameters queryParameters) throws HibernateException {
        return currentEventSource.list(spec, queryParameters);
    }

    public List listCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) throws HibernateException {
        return currentEventSource.listCustomQuery(customQuery, queryParameters);
    }

    public List listFilter(Object collection, String filter, QueryParameters queryParameters) throws HibernateException {
        return currentEventSource.listFilter(collection, filter, queryParameters);
    }

    public ScrollableResults scroll(String query, QueryParameters queryParameters) throws HibernateException {
        return currentEventSource.scroll(query, queryParameters);
    }

    public ScrollableResults scroll(CriteriaImpl criteria, ScrollMode scrollMode) {
        return currentEventSource.scroll(criteria, scrollMode);
    }

    public ScrollableResults scroll(NativeSQLQuerySpecification spec, QueryParameters queryParameters)
            throws HibernateException {
        return currentEventSource.scroll(spec, queryParameters);
    }

    public ScrollableResults scrollCustomQuery(CustomQuery customQuery, QueryParameters queryParameters)
            throws HibernateException {
        return currentEventSource.scrollCustomQuery(customQuery, queryParameters);
    }

    public void setAutoClear(boolean enabled) {
        currentEventSource.setAutoClear(enabled);
    }

    public void setFetchProfile(String name) {
        currentEventSource.setFetchProfile(name);
    }

    /** ******* JDBC CONTEXT ********** */
    /** ******* JDBC CONTEXT ********** */
    /** ******* JDBC CONTEXT ********** */

    public void afterTransactionBegin(Transaction tx) {
        currentContext.afterTransactionBegin(tx);
    }

    public ConnectionReleaseMode getConnectionReleaseMode() {
        return currentContext.getConnectionReleaseMode();
    }

    public boolean isAutoCloseSessionEnabled() {
        return currentContext.isAutoCloseSessionEnabled();
    }

    public boolean isFlushBeforeCompletionEnabled() {
        return currentContext.isFlushBeforeCompletionEnabled();
    }

    public boolean isFlushModeNever() {
        return currentContext.isFlushModeNever();
    }

    public void managedClose() {
        currentContext.managedClose();
    }

    public void managedFlush() {
        currentContext.managedFlush();
    }

    public boolean shouldAutoClose() {
        return currentContext.shouldAutoClose();
    }

}
